(*
  Changing mesh vertex data, normals and striping redundent data.
  Custom cube class declared for vertex point identification.
  On moving these vertex modifiers, the apointed vertex follows.
*)

unit fMeshEditor;

interface

uses
  Winapi.OpenGL,
  Winapi.Windows,
  Winapi.Messages,
  System.SysUtils,
  System.Variants,
  System.Classes,
  System.Contnrs,
  System.UITypes,
  Vcl.Graphics,
  Vcl.Controls,
  Vcl.Forms,
  Vcl.Dialogs,
  Vcl.ExtCtrls,
  Vcl.ComCtrls,
  Vcl.Buttons,
  Vcl.StdCtrls,

  GLS.Scene,
  GLS.State,
  GLS.VectorFileObjects,
  GLS.FileOBJ,
  GLS.Material,
  GLS.PersistentClasses,
  GLS.VectorLists,
  GLS.MeshUtils,
  GLS.SceneViewer,

  GLS.Texture,
  GLS.Color,
  GLS.Objects,
  GLS.VectorGeometry,
  GLS.VectorTypes,
  GLS.Coordinates,

  GLS.BaseClasses;

type
  TModifierCube = class(TGLCube)
  public
    FVectorIndex: Integer;
    FMeshObjIndex: Integer;
    constructor Create(AOwner: TComponent); override;
  end;

  TFormMeshEditor = class(TForm)
    Panel2: TPanel;
    Panel3: TPanel;
    GLSceneViewer: TGLSceneViewer;
    GLScene: TGLScene;
    GLCamera: TGLCamera;
    ffPolyhedron: TGLFreeForm;
    Label1: TLabel;
    cbPolygonMode: TComboBox;
    dcModifiers: TGLDummyCube;
    chbViewPoints: TCheckBox;
    StatusBar: TStatusBar;
    GroupBox1: TGroupBox;
    chbShowAxis: TCheckBox;
    Bevel1: TBevel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    tbPos: TTrackBar;
    GroupBox2: TGroupBox;
    rbXY: TRadioButton;
    rbZY: TRadioButton;
    GLLightSource1: TGLLightSource;
    GroupBox3: TGroupBox;
    btnVertex: TBitBtn;
    btnNormals: TBitBtn;
    btnTextcoords: TBitBtn;
    btnGroups: TBitBtn;
    GLMaterialLibrary1: TGLMaterialLibrary;
    OpenMeshBtn: TButton;
    SaveMeshBtn: TButton;
    OpenDialog1: TOpenDialog;
    SaveDialog1: TSaveDialog;
    procedure FormCreate(Sender: TObject);
    procedure GLSceneViewerMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer);
    procedure GLSceneViewerMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    procedure cbPolygonModeChange(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure chbViewPointsClick(Sender: TObject);
    procedure GLSceneViewerMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer);
    procedure chbShowAxisClick(Sender: TObject);
    procedure tbPosChange(Sender: TObject);
    procedure GLSceneViewerBeforeRender(Sender: TObject);
    procedure btnVertexClick(Sender: TObject);
    procedure btnNormalsClick(Sender: TObject);
    procedure btnTextcoordsClick(Sender: TObject);
    procedure btnGroupsClick(Sender: TObject);
    procedure OpenMeshBtnClick(Sender: TObject);
    procedure SaveMeshBtnClick(Sender: TObject);
  private
    function GetPolygonMode: TGLPolygonMode;
    procedure SetPolygonMode(const Value: TGLPolygonMode);
  private
    FOldX, FOldY: Integer;
    FModifierList: TObjectList;
    FSelectedModifier: TModifierCube;
    FMoveZ: Boolean;
    FOldMouseWorldPos: TGLVector;

    // Create cubes used to modify vertex points
    procedure SetVertexModifiers;
    // Populate statusbar with object information
    procedure ShowModifierStatus(const aObj: TModifierCube);
    // Change the mesh vector property for the selected modifier.
    procedure ChangeMeshVector(const aObj: TModifierCube; const aPos: TVector4f);
    // Identify mouse position in X, Y and Z axis
    function MouseWorldPos(X, Y: Integer): TGLVector;
    // Strip redundent data, recalculate normals and faces
    procedure StripAndRecalc;
    // Set Freeform's polygon mode: line, fill or points
    property PolygonMode: TGLPolygonMode read GetPolygonMode write SetPolygonMode;
  public
    // Public declarations
  end;

var
  FormMeshEditor: TFormMeshEditor;

implementation

uses
  MeshDataFrm;

{$R *.dfm}

const
  // Default combobox index for startup
  CLinePolyMode = 1;
  // Scale dimention
  CModifierDim = 0.04;

var
  // Modifier colors
  CModColorNormal: TGLColorVector;
  CModColorSelect: TGLColorVector;

constructor TModifierCube.Create(AOwner: TComponent);
begin
  inherited;
  { Set the modifiers initial size and color }
  CubeWidth := CModifierDim;
  CubeHeight := CModifierDim;
  CubeDepth := CModifierDim;
  Material.FrontProperties.Diffuse.Color := CModColorNormal;
end;

procedure TFormMeshEditor.FormCreate(Sender: TObject);
var
  lsDir : TFileName;
  lsFileName : TFileName;
begin
  // Do initial setup
  FModifierList := TObjectList.Create;
  CModColorNormal := clrCoral;
  CModColorSelect := clrSkyBlue;

  lsDir := ExtractFileDir(Application.ExeName);
  lsFileName := lsDir + 'cube.3ds';
  { Format('%s\media\cube.3ds', [lsDir]); }
  if FileExists(lsFileName) then
  begin
    ffPolyhedron.LoadFromFile(lsFileName);
    StripAndRecalc;
    SetVertexModifiers;
  end;
  cbPolygonMode.ItemIndex := CLinePolyMode;
end;

procedure TFormMeshEditor.OpenMeshBtnClick(Sender: TObject);
begin
  if OpenDialog1.Execute then
  begin
    if FileExists(OpenDialog1.FileName) then
    begin
      ffPolyhedron.LoadFromFile(OpenDialog1.FileName);
      StripAndRecalc;
      SetVertexModifiers;
    end;
  end;
end;

procedure TFormMeshEditor.SaveMeshBtnClick(Sender: TObject);
begin
  SaveDialog1.FileName := ChangeFileExt(OpenDialog1.FileName, '.obj');
  If SaveDialog1.Execute then
    ffPolyhedron.SaveToFile(SaveDialog1.FileName);
end;

procedure TFormMeshEditor.GLSceneViewerMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  lObj: TGLBaseSceneObject;
begin
  FOldX := X;
  FOldY := Y;
  // If selecting a different modifier, change the last one's color back to default
  if Assigned(FSelectedModifier) then
    FSelectedModifier.Material.FrontProperties.Diffuse.Color := CModColorNormal;
  // Get selected objects
  if not(ssCtrl in Shift) then
    Exit;
  // Check if selected object is a modifier.
  // If so, change modifiers color as to indicated selected modifier.
  lObj := GLSceneViewer.Buffer.GetPickedObject(X, Y);
  if (lObj is TModifierCube) then
  begin
    FSelectedModifier := TModifierCube(lObj);
    FSelectedModifier.Material.FrontProperties.Diffuse.Color := CModColorSelect;
    FSelectedModifier.NotifyChange(FSelectedModifier);
    ShowModifierStatus(TModifierCube(lObj));

    FMoveZ := rbZY.Checked;
    FOldMouseWorldPos := MouseWorldPos(X, Y);
  end;
end;

procedure TFormMeshEditor.GLSceneViewerMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
  lCurrentPos: TGLVector;
  lOldV: TVector3f;
  lDiff: TVector4f;
begin
  // If ctrl is not in use, move around freeform
  if (ssLeft in Shift) and (not(ssCtrl in Shift)) then
  begin
    GLCamera.MoveAroundTarget(FOldY - Y, FOldX - X);
    FOldX := X;
    FOldY := Y;
    Exit;
  end;

  // Move modifier and change relevant vertex data
  if (ssLeft in Shift) then
  begin
    FMoveZ := rbZY.Checked;

    lCurrentPos := MouseWorldPos(X, Y);
    if Assigned(FSelectedModifier) and (VectorNorm(FOldMouseWorldPos) <> 0) then
    begin
      MakeVector(lOldV, FSelectedModifier.Position.X, FSelectedModifier.Position.Y,
        FSelectedModifier.Position.Z);
      lDiff := VectorSubtract(lCurrentPos, FOldMouseWorldPos);
      FSelectedModifier.Position.Translate(lDiff);
      ChangeMeshVector(FSelectedModifier, lDiff);
    end;
    FOldMouseWorldPos := lCurrentPos;
  end;
end;

function TFormMeshEditor.GetPolygonMode: TGLPolygonMode;
begin
  Result := ffPolyhedron.Material.PolygonMode;
end;

procedure TFormMeshEditor.SetPolygonMode(const Value: TGLPolygonMode);
begin
  ffPolyhedron.Material.PolygonMode := Value;
end;

procedure TFormMeshEditor.cbPolygonModeChange(Sender: TObject);
begin
  PolygonMode := TGLPolygonMode(cbPolygonMode.ItemIndex);
end;

procedure TFormMeshEditor.SetVertexModifiers;
  procedure ScaleVector(var V1, V2: TVector3f);
  begin
    V1.X := V1.X * V2.X;
    V1.Y := V1.Y * V2.Y;
    V1.Z := V1.Z * V2.Z;
  end;

var
  i, j: Integer;
  lVector, lScale: TVector3f;
  lModifier: TModifierCube;
begin
  FModifierList.Clear;
  GLScene.BeginUpdate;
  try
    with ffPolyhedron.MeshObjects do
    begin
      for i := 0 to Count - 1 do
        for j := 0 to Items[i].Vertices.Count - 1 do
        begin
          lVector := Items[i].Vertices.Items[j];
          lModifier := TModifierCube.Create(nil);
          lModifier.FVectorIndex := j;
          lModifier.FMeshObjIndex := i;

          FModifierList.Add(lModifier);
          GLScene.Objects.AddChild(lModifier);

          lScale := ffPolyhedron.Scale.AsAffineVector;
          ScaleVector(lVector, lScale);
          lModifier.Position.Translate(lVector);
        end;
    end;
  finally
    GLScene.EndUpdate;
  end;
end;

procedure TFormMeshEditor.chbViewPointsClick(Sender: TObject);
var
  i: Integer;
begin
  GLScene.BeginUpdate;
  try
    for i := 0 to FModifierList.Count - 1 do
      TModifierCube(FModifierList.Items[i]).Visible := chbViewPoints.Checked;
  finally
    GLScene.EndUpdate;
  end;
end;

procedure TFormMeshEditor.ShowModifierStatus(const aObj: TModifierCube);
begin
  if aObj = nil then
    StatusBar.Panels[0].Text := ''
  else
    StatusBar.Panels[0].Text := Format('Modifier vector index [%d]', [aObj.FVectorIndex]);
end;

function TFormMeshEditor.MouseWorldPos(X, Y: Integer): TGLVector;
var
  v: TGLVector;
begin
  Y := GLSceneViewer.Height - Y;

  if Assigned(FSelectedModifier) then
  begin
    SetVector(v, X, Y, 0);
    if FMoveZ then
      GLSceneViewer.Buffer.ScreenVectorIntersectWithPlaneXZ(v, FSelectedModifier.Position.Y, Result)
    else
      GLSceneViewer.Buffer.ScreenVectorIntersectWithPlaneXY(v, FSelectedModifier.Position.Z, Result);
  end
  else
    SetVector(Result, NullVector);
end;

procedure TFormMeshEditor.ChangeMeshVector(const aObj: TModifierCube; const aPos: TVector4f);
var
  lVIndex, lMIndex: Integer;
  v: TVector3f;
begin
  if aObj = nil then
    Exit;

  lVIndex := aObj.FVectorIndex;
  lMIndex := aObj.FMeshObjIndex;

  // Get new vertex position, keep freeform scale in mind and redraw freeform.
  MakeVector(v, aPos.X / CModifierDim, aPos.Y / CModifierDim, aPos.Z / CModifierDim);
  ffPolyhedron.MeshObjects.Items[lMIndex].Vertices.TranslateItem(lVIndex, v);
  ffPolyhedron.StructureChanged;
end;

procedure TFormMeshEditor.GLSceneViewerMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if Assigned(FSelectedModifier) then
  begin
    FSelectedModifier.Material.FrontProperties.Diffuse.Color := CModColorNormal;
    FSelectedModifier := nil;
    // Recalculate structure and redraw freeform
    StripAndRecalc;
    // Reset vertex modifiers and their data.
    SetVertexModifiers;
  end;
end;

procedure TFormMeshEditor.chbShowAxisClick(Sender: TObject);
begin
  dcModifiers.ShowAxes := TCheckBox(Sender).Checked;
end;

procedure TFormMeshEditor.tbPosChange(Sender: TObject);
begin
  GLCamera.Position.Z := tbPos.Position;
end;

procedure TFormMeshEditor.GLSceneViewerBeforeRender(Sender: TObject);
begin
  glEnable(GL_NORMALIZE);
end;

procedure TFormMeshEditor.btnVertexClick(Sender: TObject);
var
  i, j: Integer;
  lList: TStringList;
  lVector: TVector3f;
begin
  lList := TStringList.Create;
  try
    with ffPolyhedron.MeshObjects do
      for i := 0 to Count - 1 do
      begin
        lList.Add('For mesh object ' + IntToStr(i));
        for j := 0 to Items[i].Vertices.Count - 1 do
        begin
          lVector := Items[i].Vertices.Items[j];
          lList.Add(Format('%f %f %f', [lVector.X, lVector.Y, lVector.Z]));
        end;
      end;
    ShowMeshData(lList);
  finally
    FreeAndNil(lList);
  end;
end;

procedure TFormMeshEditor.btnNormalsClick(Sender: TObject);
var
  i, j: Integer;
  lList: TStringList;
  lVector: TVector3f;
begin
  lList := TStringList.Create;
  try
    with ffPolyhedron.MeshObjects do
      for i := 0 to Count - 1 do
      begin
        lList.Add('For mesh object ' + IntToStr(i));
        for j := 0 to Items[i].Normals.Count - 1 do
        begin
          lVector := Items[i].Normals.Items[j];
          lList.Add(Format('%f %f %f', [lVector.X, lVector.Y, lVector.Z]));
        end;
      end;
    ShowMeshData(lList);
  finally
    FreeAndNil(lList);
  end;
end;

procedure TFormMeshEditor.btnTextcoordsClick(Sender: TObject);
var
  i, j    : NativeInt;
  lList   : TStringList;
  lVector : TVector3f;
begin
  lList := TStringList.Create;
  try
    with ffPolyhedron.MeshObjects do
      for i := 0 to Count - 1 do
      begin
        lList.Add('For mesh object ' + IntToStr(i));
        for j := 0 to Items[i].TexCoords.Count - 1 do
        begin
          lVector := Items[i].TexCoords.Items[j];
          lList.Add(Format('%f %f %f', [lVector.X, lVector.Y, lVector.Z]));
        end;
      end;
    ShowMeshData(lList);
  finally
    FreeAndNil(lList);
  end;
end;

procedure TFormMeshEditor.btnGroupsClick(Sender: TObject);
var
  i: Integer;
  lList: TStringList;
begin
  lList := TStringList.Create;
  try
    with ffPolyhedron.MeshObjects do
      for i := 0 to Count - 1 do
      begin
        lList.Add('For mesh object ' + IntToStr(i));
        lList.Add(IntToStr(Items[i].TriangleCount));
      end;
    ShowMeshData(lList);
  finally
    FreeAndNil(lList);
  end;
end;

procedure TFormMeshEditor.StripAndRecalc;
var
  lTrigList, lNormals: TGLAffineVectorList;
  lIndices: TGLIntegerList;
  lObj: TGLMeshObject;
  lStrips: TGLPersistentObjectList;

  lFaceGroup: TFGVertexIndexList;
  i: Integer;
begin
  // Extract raw triangle data to work with.
  lTrigList := ffPolyhedron.MeshObjects.ExtractTriangles;

  // Builds a vector-count optimized indices list.
  lIndices := BuildVectorCountOptimizedIndices(lTrigList);
  // Alter reference/indice pair and removes unused reference values.
  RemapAndCleanupReferences(lTrigList, lIndices);
  // Calculate normals.
  lNormals := BuildNormals(lTrigList, lIndices);

  // Strip where posible.
  lStrips := StripifyMesh(lIndices, lTrigList.Count, True);

  // Clear current mesh object data.
  ffPolyhedron.MeshObjects.Clear;

  // Setup new mesh object.
  lObj := TGLMeshObject.CreateOwned(ffPolyhedron.MeshObjects);
  lObj.Vertices := lTrigList;
  lObj.Mode := momFaceGroups;
  lObj.Normals := lNormals;

  for i := 0 to lStrips.Count - 1 do
  begin
    lFaceGroup := TFGVertexIndexList.CreateOwned(lObj.FaceGroups);
    lFaceGroup.VertexIndices := (lStrips[i] as TGLIntegerList);
    if i > 0 then
      lFaceGroup.Mode := fgmmTriangleStrip
    else
      lFaceGroup.Mode := fgmmTriangles;
    lFaceGroup.MaterialName := IntToStr(i and 15);
  end;
  // Redraw freeform
  ffPolyhedron.StructureChanged;

  lTrigList.Free;
  lNormals.Free;
  lIndices.Free;
end;

procedure TFormMeshEditor.FormDestroy(Sender: TObject);
begin
  FModifierList.Clear;
  FreeAndNil(FModifierList);
end;

end.
